#include "lb_batt.h"

#include <stdint.h>
#include <string.h>
#include "nordic_common.h"
#include "nrf.h"
#include "app_error.h"
#include "nrf_gpio.h"
#include "nrf51_bitfields.h"
#include "ble_bas.h"
#include "app_util.h"
#include "app_timer.h"
#include "boards.h"
#include "config.h"
#include "log.h"
#include "bsp.h"

#define VOLTAGE_AVG_NUM 6
#define ARRAY_LEN(a)            (sizeof(a)/sizeof(a[0]))          /* calculate array length */

ble_bas_t    m_bas;

static uint8_t percentage_batt_lvl = 0;
static app_timer_id_t m_battery_timer_id;                               /**< Battery timer. */

static const float VoltageTable[] = {4142.2,4011.2,3912.9,3831.0,3774.9,3734.4,3688.4,3605.3};
static const uint8_t PercentageTable[] = {100,86,72,58,43,29,15,1};

static charger_status old_charge_status, m_charge_status = NO_CHARGER;

uint8_t cal_percentage(uint16_t volatage)
{
    uint8_t length = ARRAY_LEN(VoltageTable);
    uint8_t i  = 0;
    
    //find the first value which is < volatage
    for(i = 0; i< length ; ++i)
    {
        if(volatage >= VoltageTable[i])
        {
            break;
        }
    }
    
    if( i>= length)
    {
        return 0;
    }

    if(i == 0)
    {
        return 100;
    }
    
    return (volatage - VoltageTable[i])/((VoltageTable[i-1] - VoltageTable[i])/(PercentageTable[i-1] - PercentageTable[i])) + PercentageTable[i];
}

void battery_start(void)
{
    NRF_ADC->ENABLE     = ADC_ENABLE_ENABLE_Enabled;

    NRF_ADC->EVENTS_END  = 0;    // Stop any running conversions.
    NRF_ADC->TASKS_START = 1;
}

/**@brief ADC interrupt handler.
 * @details  This function will fetch the conversion result from the ADC, convert the value into
 *           percentage and send it to peer.
 */
void ADC_IRQHandler(void)
{
    uint32_t adc_result = 0;
    uint32_t batt_lvl_in_milli_volts;
    
    if (NRF_ADC->EVENTS_END != 0)
    {
        NRF_ADC->EVENTS_END     = 0;
        adc_result              = NRF_ADC->RESULT;
        NRF_ADC->TASKS_STOP     = 1;

        batt_lvl_in_milli_volts = ADC_RESULT_IN_MILLI_VOLTS(adc_result);

//        battery_voltage_arrary[0] = 0;
        
        //g_battery_voltage_mv = batt_lvl_in_milli_volts & 0xffff;
        //LOG_X("batt_lvl_in_milli_volts:%d",batt_lvl_in_milli_volts);
        
        percentage_batt_lvl = cal_percentage(batt_lvl_in_milli_volts & 0xffff);
        
        if(percentage_batt_lvl < 5)
        {
            bsp_indication_set(BSP_INDICATE_LOW_BATTERY);
        }
        
    }
    NRF_ADC->ENABLE = ADC_ENABLE_ENABLE_Disabled;
}


void battery_adc_dev_init(void)
{
    // Configure ADC
    //NRF_ADC->POWER = ADC_POWER_POWER_Enabled << ADC_POWER_POWER_Pos;
    
    NRF_ADC->CONFIG     = BATTERY_ADC_RESOLUTION << ADC_CONFIG_RES_Pos    |
                          ADC_CONFIG_INPSEL_AnalogInputNoPrescaling  << ADC_CONFIG_INPSEL_Pos |
                          ADC_CONFIG_REFSEL_VBG << ADC_CONFIG_REFSEL_Pos |
                          ADC_INPUT_CHANNEL << ADC_CONFIG_PSEL_Pos;// |
                          //ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_None;

    NRF_ADC->EVENTS_END = 0;
}


void battery_adc_interrupt_init(void)
{
    NRF_ADC->INTENSET  = ADC_INTENSET_END_Msk;
    NVIC_SetPriority(ADC_IRQn,1);
    NVIC_EnableIRQ(ADC_IRQn);
}

/**@brief Battery measurement timer timeout handler.
 *
 * @details This function will be called each time the battery level measurement timer expires.
 *
 * @param[in]   p_context   Pointer used for passing some arbitrary information (context) from the
 *                          app_start_timer() call to the timeout handler.
 */
static void battery_level_meas_timeout_handler(void * p_context)
{
    uint32_t err_code;
    UNUSED_PARAMETER(p_context);
    battery_start();
    
    //LOG_X("battery_level_meas_timeout_handler");
    
    err_code = ble_bas_battery_level_update(&m_bas, --percentage_batt_lvl);
    
    //LOG_X("err_code:%X, Percent:%d, bas->handle:%d", err_code, percentage_batt_lvl, m_bas.conn_handle);
    
    if ((err_code != NRF_SUCCESS) &&
        (err_code != NRF_ERROR_INVALID_STATE) &&
        (err_code != BLE_ERROR_NO_TX_BUFFERS) &&
        (err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)
    )
    {
        APP_ERROR_HANDLER(err_code);
    }
}


void battery_measure_init(void)
{
    uint32_t err_code;

    NRF_ADC->ENABLE     = ADC_ENABLE_ENABLE_Disabled;
    
    battery_adc_dev_init();
    
    battery_adc_interrupt_init();

    // Create battery timer
    err_code = app_timer_create(&m_battery_timer_id,
                                APP_TIMER_MODE_REPEATED,
                                battery_level_meas_timeout_handler);
    APP_ERROR_CHECK(err_code);
}


void battery_measure_timer_start(uint32_t interval)
{
    uint32_t err_code;

    // Start battery timer
    err_code = app_timer_start(m_battery_timer_id, interval, NULL);
    APP_ERROR_CHECK(err_code);
}


void battery_measure_timer_stop()
{
    uint32_t err_code;

    // Stop battery timer
    err_code = app_timer_stop(m_battery_timer_id);
    APP_ERROR_CHECK(err_code);
}


void init_bat_service(void)
{
    uint32_t       err_code;
    ble_bas_init_t bas_init;
    
    battery_measure_init();
    
    battery_measure_timer_start(BATT_CHECK_INTERVAL);
    
    // Initialize Battery Service.
    memset(&bas_init, 0, sizeof(bas_init));

    // Here the sec level for the Battery Service can be changed/increased.
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&bas_init.battery_level_char_attr_md.cccd_write_perm);
    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&bas_init.battery_level_char_attr_md.read_perm);
    BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&bas_init.battery_level_char_attr_md.write_perm);

    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&bas_init.battery_level_report_read_perm);

    bas_init.evt_handler          = NULL;
    bas_init.support_notification = true;
    bas_init.p_report_ref         = NULL;
    bas_init.initial_batt_level   = 100;

    err_code = ble_bas_init(&m_bas, &bas_init);
    APP_ERROR_CHECK(err_code);
}


charger_status check_charger_status(void)
{
    if(0 == nrf_gpio_pin_read(CHARGER_CONNECT_PIN))
    {
        if(0 == nrf_gpio_pin_read(CHARGER_PIN))
            return CHARGING;
        else
            return CHARGE_COMPLETE;
    }
    else
    {
        return NO_CHARGER;
    }
}


void check_charger(void)
{
	m_charge_status = check_charger_status();
	
	if(old_charge_status != m_charge_status)
	{
		LOG_X("Charger:%d", m_charge_status);
		switch(m_charge_status)
		{
			case CHARGE_COMPLETE:
				bsp_indication_set(BSP_INDICATE_CHAGE_COMPLETE);
				break;
			case CHARGING:
				bsp_indication_set(BSP_INDICATE_CHAGING);
				break;
			case NO_CHARGER:
				bsp_indication_set(BSP_INDICATE_NO_CHARGER);
			default:
				break;
		}
		old_charge_status = m_charge_status;
	}
}
